from sympy import *
from sympy.calculus.util import continuous_domain
from sympy.abc import x, y, z


def function_monotonyset(f, x, interval=Reals, dir="+-"):
    """
    This function return the function f of x 's
    monotony sets.
    if f in the monotony set is increase
    """

    """
        there is a question: how can we judge
        the function on one interval is increasing
        monotony or decreasing monotony?

        my answer is:
        if we can get the second diff function g, 
        if g is bigger than zero at bottom point,
        the left is decreasing and the return is on the contrary.
        but it will get wrong answer for functions  like y=(x-3)**6 at point 3.

        we can also compare the two function values.
    """
    if interval == Reals:
        interval = continuous_domain(f, x, Reals)
    # we should first check the interval's edge.
    if interval.left_open:
        interval_inf_value = limit(f, x, interval.inf, "+")
    else:
        interval_inf_value = f.subs(x, interval.inf)

    if interval.right_open:
        interval_sup_value = limit(f, x, interval.sup, "-")
    else:
        interval_sup_value = f.subs(x, interval.sup)

    decrease_sets = []
    increase_sets = []

    zero_points_set = solveset(diff(f, x), x, interval)
    """
    check the sets if we can handle it.
    """
    # handle the monotony function.
    if zero_points_set == EmptySet:
        if interval_inf_value < interval_sup_value:
            return {"+": Interval.open(interval.inf, interval.sup), "-": decrease_sets}
        else:
            return {"+": increase_sets, "-": Interval.open(interval.inf, interval.sup)}
    # we should check if the zero_points_set is FiniteSet or other type of Set we can handle.
    # or it will return something wrong.
    set_type = type(zero_points_set)
    if set_type == Union:
        pass

    if set_type == FiniteSet:
        x_list = list(zero_points_set)
        x_list.sort()
    else:
        raise NotImplementedError(
            "\n".join(
                [
                    "We haven't handle the type of diff(f)'s solveset type:",
                    str(set_type),
                    "please wait.",
                ]
            )
        )
    # if the set is ImageSet or it's Union,
    # it can also work, but I don't how to
    # operate it now.

    # add the edge point to x_list
    # and add the f's value on the points

    f_value_list = []
    for points in x_list:
        f_value_list.append(f.subs(x, points))

    if interval.inf < x_list[0]:
        x_list.insert(0, interval.inf)
        f_value_list.insert(0, interval_inf_value)

    if interval.sup > x_list[-1]:
        x_list.append(interval.sup)
        f_value_list.append(interval_sup_value)

    for i in range(len(x_list) - 1):
        if f_value_list[i] < f_value_list[i + 1]:
            increase_sets.append(Interval.open(x_list[i], x_list[i + 1]))
        else:
            decrease_sets.append(Interval.open(x_list[i], x_list[i + 1]))

    return {"+": increase_sets, "-": decrease_sets}


def function_min(f, x, interval=Reals, with_x=False):
    """
    If you use the triangle function, it may have errors
    for can't judge the solve is in Reals.
    You can use trigsimp to simplify the expression of x.
    It may solve the problem.
    """
    # fix the problem of function solve.
    x_sets = solveset(diff(f, x), x, interval)
    x_list = []
    for item in x_sets:
        x_list.append(item)

    check_function = diff(f, x, x)

    x_list_length = len(x_list)
    if with_x == True:
        answer = {}
        for i in range(x_list_length):
            if (check_function.subs(x, x_list[i])) > 0:
                answer[x_list[i]] = f.subs(x, x_list[i])
    else:
        answer = []
        for i in range(x_list_length):
            if (check_function.subs(x, x_list[i])) > 0:
                answer.append(f.subs(x, x_list[i]))

    return answer


def function_max(f, x, interval=Reals, with_x=False):
    """
    If you use the triangle function, it may have errors
    for can't judge the solve is in Reals.
    You can use trigsimp to simplify the expression of x.
    It may solve the problem.
    """
    # fix the problem of function solve.
    x_sets = solveset(diff(f, x), x, interval)
    x_list = []
    for item in x_sets:
        x_list.append(item)

    check_function = diff(f, x, x)

    x_list_length = len(x_list)
    if with_x == True:
        answer = {}
        for i in range(x_list_length):
            # If the x is not in Reals it has no meaning.
            if not (x_list[i] in Reals):
                continue
            if (check_function.subs(x, x_list[i])) < 0:
                answer[x_list[i]] = f.subs(x, x_list[i])
    else:
        answer = []
        for i in range(x_list_length):
            # If the x is not in Reals it has no meaning.
            if (check_function.subs(x, x_list[i])) < 0:
                answer.append(f.subs(x, x_list[i]))
    return answer


def function_tangent(f, x, x_0=0, coordinate=(x, y)):
    """
    this function will return the f(function of x)'s tangent equation at x=x_0.

    if you don't point out the the x_0,it will be 0.
    if you don't point out the coordinate's symbols, it will be xOy coordinate.
    """
    y_0 = simplify(f.subs(x, x_0))
    k = simplify(diff(f, x).subs(x, x_0))
    return Eq(coordinate[1], simplify(k * (coordinate[0] - x_0) + y_0))
